package cn.dansj.common.utils.json;

import cn.dansj.common.utils.exception.DeserializationException;
import cn.dansj.common.utils.exception.SerializationException;
import cn.dansj.common.utils.transfer.ArrayUtils;
import cn.dansj.common.utils.transfer.Transformation;
import cn.dansj.common.utils.transfer.Verification;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.*;

import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

public abstract class JSONUtils {
    /**
     * Object to json string.
     *
     * @param obj obj
     * @return json string
     */
    public static String toJSON(final Object obj, Feature... features) {
        // 字符串类型不是json格式的直接返回
        if (obj instanceof String && !isJSON((String) obj)) return (String) obj;
        // json格式的字符串先转为jsonNode
        final Object object = obj instanceof String ? toObj((String) obj) : obj;
        final Feature[] defaultFeatures = CommonUtils.addDefaultFeatures(features);
        return Transformation.lambdaTryCatch(() -> {
            ObjectMapper mapper = ObjectMapperFactory.getObjectMapperInstance(defaultFeatures);
            if (Feature.featureEnumSet(defaultFeatures).contains(Feature.PRETTY_PRINT)) {
                return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            } else {
                return mapper.writeValueAsString(object);
            }
        }, Transformation.emptyStringSupplier).replaceAll("^\"|\"$", "");
    }

    /**
     * Object to json string byte array.
     *
     * @param obj obj
     * @return json string byte array
     * @throws SerializationException if transfer failed
     */
    public static byte[] toJSONBytes(Object obj, Feature... features) {
        return Transformation.lambdaTryCatch(() -> ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(features)).writeValueAsBytes(obj), () -> new byte[]{});
    }

    /**
     * Json string deserialize to Object.
     *
     * @param json json string
     * @param cls  class of object
     * @param <T>  General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(byte[] json, Class<T> cls, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(cls, features)).readValue(json, cls);
        } catch (Exception e) {
            throw new DeserializationException(cls, e);
        }
    }

    /**
     * Json string deserialize to Object.
     *
     * @param json json string
     * @param type {@link Type} of object
     * @param <T>  General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(byte[] json, Type type, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(type, features)).readValue(json, CommonUtils.constructType(type));
        } catch (Exception e) {
            throw new DeserializationException(e);
        }
    }

    /**
     * Json string deserialize to Object.
     *
     * @param inputStream json string input stream
     * @param cls         class of object
     * @param <T>         General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(InputStream inputStream, Class<T> cls, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(cls, features)).readValue(inputStream, cls);
        } catch (IOException e) {
            throw new DeserializationException(e);
        }
    }

    /**
     * Json string deserialize to Object.
     *
     * @param json          json string byte array
     * @param typeReference {@link TypeReference} of object
     * @param <T>           General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(byte[] json, TypeReference<T> typeReference, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(typeReference, features)).readValue(json, typeReference);
        } catch (Exception e) {
            throw new DeserializationException(e);
        }
    }

    /**
     * Json string deserialize to Object.
     *
     * @param json          json string
     * @param typeReference {@link TypeReference} of object
     * @param <T>           General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(String json, TypeReference<T> typeReference, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(typeReference, features)).readValue(json, typeReference);
        } catch (IOException e) {
            throw new DeserializationException(typeReference.getClass(), e);
        }
    }

    public static <T> T toObj(Object obj, TypeReference<T> typeReference, Feature... features) {
        if (obj instanceof String) return toObj((String) obj, typeReference, features);
        return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(typeReference, features)).convertValue(obj, typeReference);
    }

    /**
     * Json string deserialize to Object.
     *
     * @param json json string
     * @param cls  class of object
     * @param <T>  General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(String json, Class<T> cls, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(cls, features)).readValue(json, cls);
        } catch (IOException e) {
            throw new DeserializationException(cls, e);
        }
    }

    public static <T> T toObj(Object obj, Class<T> cls, Feature... features) {
        if (obj instanceof String) return toObj((String) obj, cls, features);
        return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(cls, features)).convertValue(obj, cls);
    }

    /**
     * Json string deserialize to Object.
     *
     * @param json json string
     * @param type {@link Type} of object
     * @param <T>  General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(String json, Type type, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(type, features)).readValue(json, CommonUtils.constructType(type));
        } catch (IOException e) {
            throw new DeserializationException(e);
        }
    }

    /**
     * Json string deserialize to Object.
     *
     * @param inputStream json string input stream
     * @param type        {@link Type} of object
     * @param <T>         General type
     * @return object
     * @throws DeserializationException if deserialize failed
     */
    public static <T> T toObj(InputStream inputStream, Type type, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(type, features)).readValue(inputStream, CommonUtils.constructType(type));
        } catch (IOException e) {
            throw new DeserializationException(type, e);
        }
    }

    public static <T> T toObj(Object obj, Type type, Feature... features) {
        if (obj instanceof String) return toObj((String) obj, type, features);
        return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(type, features)).convertValue(obj, CommonUtils.constructType(type));
    }

    /**
     * Json string deserialize to Jackson {@link JsonNode}.
     *
     * @param json json string
     * @return {@link JsonNode}
     * @throws DeserializationException if deserialize failed
     */
    public static JSONNode toObj(String json, Feature... features) {
        if (json == null) return JSONNode.parse(NullNode.getInstance());
        if (!isJSON(json) && !isArray(json) && !Transformation.isNumeric(json)) {
            return JSONNode.parse(TextNode.valueOf(json));
        }

        try {
            return JSONNode.parse(ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(features)).readTree(json));
        } catch (IOException e) {
            throw new DeserializationException(e);
        }
    }

    public static JSONNode toObj(Object obj, Feature... features) {
        if (obj == null) return JSONNode.parse(NullNode.getInstance());
        if (obj instanceof JSONNode) return (JSONNode) obj;
        if (obj instanceof String) return toObj((String) obj, features);
        return toObj(obj, JSONNode.class, features);
    }

    /**
     * Register subType for child class.
     *
     * @param clz  child class
     * @param type type name of child class
     */
    public static void registerSubtype(Class<?> clz, String type) {
        ObjectMapperFactory.getObjectMapperInstance().registerSubtypes(new NamedType(clz, type));
    }

    /**
     * Create a new empty Jackson {@link ObjectNode}.
     *
     * @return {@link ObjectNode}
     */
    public static ObjectNode createEmptyJsonNode() {
        return new ObjectNode(ObjectMapperFactory.getObjectMapperInstance().getNodeFactory());
    }

    /**
     * Create a new empty Jackson {@link ArrayNode}.
     *
     * @return {@link ArrayNode}
     */
    public static ArrayNode createEmptyArrayNode() {
        return new ArrayNode(ObjectMapperFactory.getObjectMapperInstance().getNodeFactory());
    }

    /**
     * construct java type -> Jackson Java Type.
     *
     * @param type java type
     * @return JavaType {@link JavaType}
     */
    public static JavaType constructJavaType(Type type) {
        return CommonUtils.constructType(type);
    }

    /**
     * 对象深拷贝
     */
    @SuppressWarnings("unchecked")
    public static <T> T deepClone(T obj) {
        if (obj == null) return null;
        return toObj(toJSON(obj), (Class<T>) obj.getClass());
    }

    /**
     * 将JsonNode转换为指定类型对象
     */
    public static <T> T treeToValue(JsonNode jsonNode, Class<T> clazz, Feature... features) {
        try {
            return ObjectMapperFactory.getObjectMapperInstance(CommonUtils.addDefaultFeatures(clazz, features)).treeToValue(jsonNode, clazz);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JsonNode转对象失败", e);
        }
    }

    public static <T> T treeToValue(JSONNode jsonNode, Class<T> clazz, Feature... features) {
        return treeToValue(jsonNode.getJsonNode(), clazz, features);
    }

    public static JSONNode from(String k1, Object v1) {
        ObjectNode jsonNode = createEmptyJsonNode();
        addNode(jsonNode, k1, v1);
        return JSONNode.parse(jsonNode);
    }

    public static JSONNode from(String k1, Object v1, String k2, Object v2) {
        ObjectNode jsonNode = createEmptyJsonNode();
        addNodes(jsonNode, k1, v1, k2, v2);
        return JSONNode.parse(jsonNode);
    }

    public static JSONNode from(String k1, Object v1, String k2, Object v2, String k3, Object v3) {
        ObjectNode jsonNode = createEmptyJsonNode();
        addNodes(jsonNode, k1, v1, k2, v2, k3, v3);
        return JSONNode.parse(jsonNode);
    }

    public static JSONNode from(String k1, Object v1, String k2, Object v2, String k3, Object v3, String k4, Object v4) {
        ObjectNode jsonNode = createEmptyJsonNode();
        addNodes(jsonNode, k1, v1, k2, v2, k3, v3, k4, v4);
        return JSONNode.parse(jsonNode);
    }

    public static JSONNode from(String k1, Object v1, String k2, Object v2, String k3, Object v3, String k4, Object v4, String k5, Object v5) {
        ObjectNode jsonNode = createEmptyJsonNode();
        addNodes(jsonNode, k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
        return JSONNode.parse(jsonNode);
    }

    public static JSONNode from(String k1, Object v1, String k2, Object v2, String k3, Object v3, String k4, Object v4, String k5, Object v5, Object... kvArgs) {
        ObjectNode jsonNode = createEmptyJsonNode();
        addNodes(jsonNode, k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, kvArgs);
        return JSONNode.parse(jsonNode);
    }

    private static void addNodes(ObjectNode jsonNode, Object... objects) {
        int size = objects.length;

        if ((size & 1) == 1) {
            // 如果number不为偶数, 长度减1, 对应的值默认为null
            size -= 1;
        }

        int keyIndex = 0;
        int valueIndex = 1;
        while (size >= valueIndex) {
            addNode(jsonNode, String.valueOf(objects[keyIndex]), objects[valueIndex]);
            keyIndex += 2;
            valueIndex += 2;
        }
    }

    private static void addNode(ObjectNode objectNode, String key, Object value) {
        objectNode.set(key, ObjectMapperFactory.getObjectMapperInstance().valueToTree(value));
    }

    public static JSONNode from(Object object, Feature... features) {
        if (object == null) return JSONNode.parse(createEmptyJsonNode());
        if (object instanceof JSONNode) return (JSONNode) object;
        if (object instanceof JsonNode) return JSONNode.parse((JsonNode) object);
        if (object instanceof String) {
            String obj = (String) object;
            return isJSON(obj) || isArray(obj) ? toObj(obj, features) : JSONNode.parse(createEmptyJsonNode());
        } else if (object.getClass().isArray()) {
            ObjectNode jsonNode = createEmptyJsonNode();
            addNodes(jsonNode, (Object[]) object);
            return JSONNode.parse(jsonNode);
        } else if (object instanceof Collection<?>) {
            ObjectNode jsonNode = createEmptyJsonNode();
            addNodes(jsonNode, ((Collection<?>) object).toArray());
            return JSONNode.parse(jsonNode);
        } else {
            return Transformation.lambdaTryCatch(() -> toObj(toJSON(object, features), features), () -> JSONNode.parse(createEmptyJsonNode()));
        }
    }

    /**
     * 判断字符串是否是有效的 JSON 对象
     *
     * @param str 输入字符串
     * @return true-是JSON对象，false-不是
     */
    public static boolean isJSON(String str) {
        if (Verification.checkNull(str) || !looksLikeJson(str)) return false;
        return Transformation.lambdaTryCatch(() -> ObjectMapperFactory.getObjectMapperInstance().readTree(str).isObject(), () -> false);
    }

    private static boolean looksLikeJson(String str) {
        if (str.length() < 2) return false;
        char firstChar = str.trim().charAt(0);
        char lastChar = str.trim().charAt(str.trim().length() - 1);
        return String.format("%s%s", firstChar, lastChar).equals("{}");
    }

    private static boolean looksLikeArray(String str) {
        if (str.length() < 2) return false;
        char firstChar = str.trim().charAt(0);
        char lastChar = str.trim().charAt(str.trim().length() - 1);
        return String.format("%s%s", firstChar, lastChar).equals("[]");
    }

    /**
     * 判断字符串是否是有效的 List 对象
     *
     * @param str 输入字符串
     * @return true-是List对象，false-不是
     */
    public static boolean isArray(String str) {
        if (Verification.checkNull(str) || !looksLikeArray(str)) return false;
        return Transformation.lambdaTryCatch(() -> ObjectMapperFactory.getObjectMapperInstance().readTree(str).isArray(), () -> false);
    }

    public static void registerModule(SimpleModule registerModule) {
        ObjectMapperFactory.getObjectMapperInstance().registerModule(registerModule);
    }

    private static JSONObject arrayToMap(Object array) {
        if (array.getClass().isArray()) {
            return JSONObject.of((Object[]) array);
        } else if (array instanceof Collection<?>) {
            return JSONObject.of(((Collection<?>) array).toArray());
        } else if (array instanceof String && isArray((String) array)) {
            return JSONObject.of(toObj((String) array, List.class).toArray());
        } else {
            return new JSONObject();
        }
    }

    private static Object transferArrayNode(ArrayNode arrayNode) {
        return ArrayUtils.asList(arrayNode.elements()).stream().map(e -> JSONUtils.toObj(e, Object.class)).collect(Collectors.toList());
    }

    private static Object transferJSONNode(JSONNode jsonNode) {
        return jsonNode.getJsonNode() instanceof ArrayNode ? transferArrayNode((ArrayNode) jsonNode.getJsonNode()) : jsonNode;
    }

    public static JSONObject toJSONObject(Object object, Feature... features) {
        Object newObject = object instanceof JSONNode ? transferJSONNode((JSONNode) object) : object instanceof ArrayNode ? transferArrayNode((ArrayNode) object) : object;
        EnumSet<Feature> featureEnumSet = Feature.featureEnumSet(features);
        return orderByKey(featureEnumSet.contains(Feature.NUMERIC_AS_STRING) ? toJSONObjectFeatureNumericAsString(newObject, features) : toJSONObjectNoFeatureNumericAsString(newObject, features), featureEnumSet.contains(Feature.ORDER_MAP_BY_KEYS));
    }

    private static JSONObject orderByKey(JSONObject jsonObject, boolean orderByKey) {
        return orderByKey ? new JSONObject(new TreeMap<>(jsonObject)) : jsonObject;
    }

    // 存在数字为字符串特性
    private static JSONObject toJSONObjectFeatureNumericAsString(Object object, Feature... features) {
        return toObj((object instanceof Collection<?> || object.getClass().isArray() || (object instanceof String && isArray((String) object))) ? arrayToMap(object) : object, JSONObject.class, features);
    }

    // 不存在数字为字符串特性
    private static JSONObject toJSONObjectNoFeatureNumericAsString(Object object, Feature... features) {
        return object instanceof JSONObject ? (JSONObject) object : object instanceof Map<?, ?> ? new JSONObject((Map<?, ?>) object) :
                (object instanceof Collection<?> || object.getClass().isArray()) ? arrayToMap(object) : object instanceof String && isArray((String) object) ? arrayToMap(object) : toObj(object, JSONObject.class, features);
    }

    public static JSONNode at(Object o, String path, Feature... features) {
        if (Verification.checkNull(o)) return JSONNode.parse(createEmptyJsonNode());
        JSONNode node = o instanceof JsonNode ? JSONNode.parse((JsonNode) o) : toObj(o, features);
        if (Verification.checkNull(path) || path.equals("$")) return node;
        // 移除开头的$
        String pointer = path.startsWith("$") ? path.trim().substring(1) : path.trim();
        pointer = pointer.replace('.', '/').replaceAll("\\[(\\d+)]", "/$1");
        // 确保以/开头
        if (!pointer.startsWith("/")) pointer = "/" + pointer;
        return node.at(JsonPointer.compile(pointer));
    }

    public static ObjectMapper getJSONMapper(Feature... features) {
        return ObjectMapperFactory.getObjectMapperInstance(features);
    }
}
